/*
 * Copyright 2013 JROSE
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.jrose.core.jdbc.persistence.support;

import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.apache.commons.lang3.Validate;
import org.jrose.core.jdbc.datasource.RoutingPolicy;
import org.jrose.core.jdbc.datasource.UseRoutingPolicy;
import org.jrose.core.jdbc.entity.field.PersistenceField;
import org.jrose.core.jdbc.persistence.executor.CountSqlExecutor;
import org.jrose.core.jdbc.persistence.executor.DeleteSqlExecutor;
import org.jrose.core.jdbc.persistence.executor.InsertSqlExecutor;
import org.jrose.core.jdbc.persistence.executor.QuerySqlExecutor;
import org.jrose.core.jdbc.persistence.executor.UpdateSqlExecutor;
import org.jrose.core.jdbc.persistence.query.Insert;
import org.jrose.core.jdbc.persistence.query.Query;
import org.jrose.core.jdbc.persistence.query.Update;
import org.jrose.core.util.CollectionUtils;
import org.jrose.core.util.ExtendedMap;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.util.Assert;

import java.io.Serializable;
import java.util.LinkedList;
import java.util.List;

@NoArgsConstructor(access = AccessLevel.PROTECTED)
abstract public class SqlExecutorProvider<E extends Serializable> extends EntityDefinitionProvider<E> {
    /**
     * Persist the specified entity.
     */
    public E persist(E entity) {
        Assert.notNull(entity, "Entity must not be null");
        return executeInsert(entity);
    }

    /**
     * Load the entity from table by specified primary keys.
     */
    @UseRoutingPolicy(RoutingPolicy.usingMaster)
    public E load(Serializable... ids) {
        validatePrimaryKey(ids);
        return executeQuery(ids);
    }

    /**
     * Load all entities from table.
     */
    public List<E> loadAll() {
        return executeQuery();
    }

    /**
     * Count all entities in table.
     */
    public long count() {
        return executeCount();
    }

    /**
     * Update the specified entity.
     */
    public void update(E entity, Serializable... ids) {
        validatePrimaryKey(ids);
        executeUpdate(entity, ids);
    }

    /**
     * Delete the entity by specified primary keys.
     */
    public void delete(Serializable... ids) {
        validatePrimaryKey(ids);
        executeDelete(ids);
    }


    // +----------------------------------------------------------------------+
    // | PERSIST OPERATIONS                                                   |
    // +----------------------------------------------------------------------+

    protected E executeInsert(E entity) {
        Validate.notNull(entity, "Entity must not be null");

        Insert<E> insert = Insert.where(entity);
        new InsertSqlExecutor<E>(databaseConnection, entityDefinition).with(insert).execute();
        return entity;
    }

    // +----------------------------------------------------------------------+
    // | SELECT OPERATIONS                                                    |
    // +----------------------------------------------------------------------+

    protected List<E> executeQuery() {
        Query<E> query = new Query<E>();
        return executeQuery(query);
    }

    protected E executeQuery(Serializable... ids) {
        Query<E> query = new Query<E>().primaryKey(ids);
        List<E> entities = executeQuery(query);
        return CollectionUtils.firstElement(entities);
    }

    protected List<E> executeQuery(Query<E> query) {
        Assert.notNull(query);
        ExtendedMap message = new QuerySqlExecutor<E>(databaseConnection, entityDefinition).with(query).execute();
        //noinspection unchecked
        return (List<E>) message.get("ENTITIES");
    }

    protected Page<E> executeQuery(Pageable pageable, Query<E> query) {
        Assert.notNull(pageable);
        Assert.notNull(query);

        query.page(pageable.getPageNumber(), pageable.getPageSize());

        ExtendedMap message = new CountSqlExecutor<E>(databaseConnection, entityDefinition).skipLimit(true).with(query).execute();
        long total = (Long) message.get("COUNT");

        message = new QuerySqlExecutor<E>(databaseConnection, entityDefinition).with(query).execute();
        //noinspection unchecked
        List<E> entities = (List<E>) message.get("ENTITIES");

        return new PageImpl<>(entities, pageable, total);
    }

    // +----------------------------------------------------------------------+
    // | COUNT OPERATIONS                                                     |
    // +----------------------------------------------------------------------+

    protected long executeCount() {
        Query<E> query = new Query<E>();
        return executeCount(query);
    }

    protected long executeCount(Query<E> query) {
        Assert.notNull(query);

        ExtendedMap message = new CountSqlExecutor<E>(databaseConnection, entityDefinition).with(query).execute();
        return (Long) message.get("COUNT");
    }

    // +----------------------------------------------------------------------+
    // | UPDATE OPERATIONS                                                    |
    // +----------------------------------------------------------------------+

    protected int executeUpdate(E entity, Serializable... ids) {
        Assert.notNull(entity);

        List<PersistenceField> updateCandidates = new LinkedList<>();
        for (PersistenceField field : entityDefinition.getAllFields()) {
            if (field.isPrimaryKeyField()) {
                continue;   // ignore primary key when updating
            }
            if (field.get(entity) != null) {
                updateCandidates.add(field);    // value is not null, add it to candiates
            }
        }
        if (updateCandidates.isEmpty()) {
            return 0;   // nothing to be updated, ignore
        }

        Update<E> update = new Update<E>();
        for (PersistenceField field : updateCandidates) {
            update = update.set(field.getFieldName(), field.get(entity));
        }
        return executeUpdate(update, ids);
    }

    protected int executeUpdate(Update<E> update, Serializable... ids) {
        Query<E> query = new Query<E>().primaryKey(ids);
        return executeUpdate(update, query);
    }

    protected int executeUpdate(Update<E> update, Query<E> query) {
        Assert.notNull(update);
        Assert.notNull(query);

        ExtendedMap message = new UpdateSqlExecutor<E>(databaseConnection, entityDefinition)
                .with(update)
                .with(query)
                .execute();
        return (Integer) message.get("ROWS");
    }

    // +----------------------------------------------------------------------+
    // | DELETE OPERATIONS                                                    |
    // +----------------------------------------------------------------------+

    protected int executeDelete(Serializable... ids) {
        Query<E> query = new Query<E>().primaryKey(ids);
        return executeDelete(query);
    }

    protected int executeDelete(Query<E> query) {
        Assert.notNull(query);

        ExtendedMap message = new DeleteSqlExecutor<E>(databaseConnection, entityDefinition).with(query).execute();
        return (Integer) message.get("ROWS");
    }
}
